Chapter 14

Mini Project: Course Catalog App

Session 14

Overview

Purpose

Build a production-ready Course Catalog app that demonstrates core Flutter skills: project structure, networking, JSON models, list/grid UI, navigation, state management, caching, search, and basic persistence.

Scope

Single mobile app (Android/iOS) with responsive layouts, offline-friendly caching, and a small set of user flows (browse, search, view details, favorite courses).

Outcome

Deliver a working APK (or run on emulator), source code organized by feature, unit tests for models/validators, and widget tests for key screens.

1

Requirements (Functional and Non-Functional)

The Course Catalog app must meet both functional and non-functional requirements.

Core Features

  • Course list with pull-to-refresh and pagination.
  • Course detail screen with full description, syllabus bullets, instructor info, and enroll/favorite button.
  • Search bar with debounced queries and live results.
  • Favorites view persisted locally.
  • Offline cache: last successful course list stored and shown when offline.
  • Error and empty states with retry options.

Non-Functional

  • Clean architecture: separation of UI, state, services, and models.
  • State management via Provider + ChangeNotifier (or Riverpod if preferred).
  • Responsive UI for narrow and wide screens.
  • Accessible buttons and clear semantic labels.
  • Automated unit tests for model parsing and at least two widget tests for list and detail flows.
2

Architecture and Folders

A well-organized project structure is essential for maintainability.

High-Level Layers

  • Presentation (screens, widgets)
  • State (providers / notifiers)
  • Domain/models (Course, Instructor, SyllabusItem)
  • Data/services (API client, local cache)

Suggested Folder Layout

lib/
  main.dart
  app.dart
  models/
    course.dart
    instructor.dart
  services/
    api_service.dart
    cache_service.dart
  providers/
    course_provider.dart
  screens/
    home_screen.dart
    course_detail_screen.dart
    favorites_screen.dart
    search_screen.dart
  widgets/
    course_card.dart
    course_list.dart
    course_filter_bar.dart
  utils/
    constants.dart
    debounce.dart

State Flow

UI → Provider (CourseProvider) → Service (ApiService / CacheService) → Models → Provider updates UI via notifyListeners.

3

Data Models and API Contract

Defining clear data models and API contracts ensures consistency.

Course Model (Required Fields)

  • id: String
  • title: String
  • subtitle: String
  • description: String
  • price: double (nullable for free)
  • imageUrl: String
  • instructor: Instructor (id, name, avatarUrl)
  • syllabus: List<String> or List<SyllabusItem>
  • rating: double
  • tags: List<String>

API Endpoints (Mockable)

  • GET /courses?page=1&limit=20&query=flutter — returns paginated list with total/count.
  • GET /courses/{id} — returns full course details.
  • Optional: GET /instructors/{id}

Local Cache

Persist last loaded page (JSON) and favorites (list of course ids) using SharedPreferences or local file. Use JSON serialization in models: toJson/fromJson.

4

UI Screens and Key Widgets

Each screen serves a specific purpose in the user journey.

HomeScreen

  • Top: AppBar with search icon and favorites shortcut.
  • Body: CourseList (ListView.builder or GridView.builder depending on width) with pull-to-refresh and infinite scroll loader.
  • Each item: CourseCard showing image, title, subtitle, rating, price, favorite icon.

CourseDetailScreen

  • Large header image, title, instructor row, rating, price, enroll button, favorite toggle, expandable syllabus list, and share button.
  • Use SliverAppBar for collapsible image on larger screens (optional).

SearchScreen (or inline search)

  • Debounced search input; shows loading, suggestions, and results.
  • Cancel previous network call on new query.

FavoritesScreen

  • Shows favorited courses from local store.
  • Allow unfavorite and tap to open details.

Shared Widgets

  • CourseCard (configurable compact/expanded)
  • CourseList (accepts items, loading state, onEndReached callback)
  • ErrorView, EmptyView, LoadingFooter
5

Step-by-Step Implementation Plan

Follow this structured approach to build the app systematically.

1. Project Bootstrap

Create flutter project, set up folder layout, add dependencies: provider, http, shared_preferences, cached_network_image, flutter_test, intl (optional). Add basic ThemeData in app.dart and wire Provider scope in main.dart.

2. Models

Implement Course and Instructor with factory fromJson(Map) and toJson(). Add simple validation in factories (throw FormatException if essential fields missing). Add unit tests to validate parsing and error handling with sample JSON.

3. Services

ApiService: implement methods to fetch courses and course details. Use http and return parsed models. Add error handling and small retry/backoff wrapper if desired. CacheService: implement read/write of cached course list and favorites using SharedPreferences or local file. Provide methods: saveCourses(json), loadCourses(), saveFavorites(List<String>), loadFavorites().

4. Provider (CourseProvider)

Expose: List<Course> courses, bool loading, bool loadingMore, String? error, List<String> favorites, methods: loadInitial(), loadMore(), refresh(), toggleFavorite(courseId), search(query). Implement pagination guards (isLoadingMore flag) and network error handling. On network success update cache.

5. UI: HomeScreen and CourseList

Build CourseCard with accessible semantics and tappable favorite icon. Use cached_network_image for images. Implement pull-to-refresh (RefreshIndicator) calling provider.refresh(). Attach ScrollController for infinite scroll, invoking provider.loadMore() when near bottom.

6. Detail Screen and Favorites

Push detail via Navigator; fetch detailed course from provider or ApiService if not present. Implement favorite toggle that updates provider and cache.

7. Search

Implement Debounce utility. Wire search input to provider.search(query) with 300–500ms debounce and a small loading indicator inside the search bar.

8. Offline and Cache Behavior

On startup, provider attempts loadInitial(): try network, on failure load cache and set offline error state. Display cached list with a banner indicating offline mode and Retry button.

9. Tests

Unit tests: model parsing, cache read/write stubs (mock SharedPreferences). Widget tests: pump HomeScreen with mocked provider that returns test courses; verify list displays and tapping course opens detail.

10. Polish and Accessibility

Add semanticsLabels for images/buttons, ensure tap targets exceed minimum size, support dark theme toggle if time permits.

11. Build and Deliver

Run flutter build apk –release, test on device. Provide README with run instructions, test coverage, and known issues.

6

Testing, Deployment, and Deliverables

Proper testing and deployment ensure a quality deliverable.

Tests

  • Unit tests for model parsing and providers' business logic (pagination guard, favorites).
  • Widget tests for HomeScreen (list displays) and CourseDetailScreen (renders key info, toggles favorite).

Deployment

  • Prepare app icon and splash screen assets.
  • For Android: set versioning in build.gradle, sign release APK or AAB, follow Play Console steps.
  • For iOS: configure Xcode, bundle id, provisioning profiles.
  • Provide an APK or instructions to run on emulator with flutter run.

Deliverables Checklist

  • Source code repository with clear commits.
  • README: setup, architecture notes, how to run tests, and design decisions.
  • Unit and widget tests passing.
  • APK or emulator run instructions.
  • Short demo video or screenshots (optional).
7

Session Assignment

Complete the full Course Catalog app with at least: Home list with pagination, Course detail screen, Search, Favorites persisted locally, Offline cache fallback, Unit tests for models, and two widget tests.

Submit: Git repo link or zipped project, README describing architecture and trade-offs (250–400 words), test results, and at least three screenshots (list, detail, favorites).